package com.dy.mongo.service;

import com.dy.mongo.document.PersonImage;
import com.dy.mongo.document.PersonText;
import com.dy.mongo.document.PersonVideo;
import com.dy.mongo.dto.BatchResult;
import com.dy.mongo.dto.ManifestRow;
import com.dy.mongo.repository.PersonImageRepository;
import com.dy.mongo.repository.PersonTextRepository;
import com.dy.mongo.repository.PersonVideoRepository;
import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvValidationException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.bson.Document;
import org.springframework.data.mongodb.gridfs.GridFsTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.stream.Stream;
import java.net.URLConnection;

/**
 * 批量导入 Zip 媒体包：内含 manifest.csv + 若干媒体文件。
 * <p>
 * 核心流程：
 * <ol>
 *   <li>接收客户端上传的 <code>zipFile</code>，保存为临时文件以便 <code>java.util.zip.ZipFile</code> 随机访问。</li>
 *   <li>解析根目录下的 <code>manifest.csv</code> → 映射 <code>filename -&gt; ManifestRow</code>。</li>
 *   <li>遍历 Zip 里的每个条目：
 *        <ul>
 *          <li>取出 ManifestRow，校验文件名是否存在清单中。</li>
 *          <li>根据 <code>type</code> 调用 <code>storeGrid</code> 写入 GridFS 或读取文本。</li>
 *          <li>构造对应的 MongoDB 文档 <code>PersonImage / PersonVideo / PersonText</code>。</li>
 *        </ul>
 *   </li>
 *   <li>对三张集合分别调用 <code>insert(List)</code>，一次性落库提升性能。</li>
 *   <li>删除临时 zip 文件；返回 <code>BatchResult</code> 统计信息。</li>
 * </ol>
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class BatchMediaImportService {

    private final GridFsTemplate gridFsTemplate;
    private final PersonImageRepository imageRepo;
    private final PersonVideoRepository videoRepo;
    private final PersonTextRepository textRepo;

    @Transactional
    public BatchResult importZip(MultipartFile zipFile) throws IOException, CsvValidationException {
        // 1. 将上传文件保存到临时文件，方便 ZipFile 随机读取
        Path tempZip = Files.createTempFile("media-import-", ".zip");
        zipFile.transferTo(tempZip);

        List<String> failed = new ArrayList<>();
        int images = 0, videos = 0, texts = 0, manifestCount = 0;

        try (ZipFile zip = new ZipFile(tempZip.toFile())) {
            // 1) 优先寻找名为 manifest.csv 的文件；
            // 2) 若不存在，则取 Zip 中首个 .csv 文件作为清单。
            ZipEntry manifestEntry = zip.getEntry("manifest.csv");
            if (manifestEntry == null) {
                manifestEntry = zip.stream()
                        .filter(e -> !e.isDirectory() && e.getName().toLowerCase().endsWith(".csv"))
                        .findFirst()
                        .orElse(null);
            }
            if (manifestEntry == null) {
                throw new IllegalArgumentException("压缩包中未找到任何 csv 清单文件");
            }

            Map<String, ManifestRow> manifest = parseManifest(zip.getInputStream(manifestEntry));
            manifestCount = manifest.size();
            String manifestName = manifestEntry.getName();

            List<PersonImage> imageDocs = new ArrayList<>();
            List<PersonVideo> videoDocs = new ArrayList<>();
            List<PersonText> textDocs  = new ArrayList<>();

            Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if (entry.isDirectory() || entry.getName().equals(manifestName)) continue;
                String filename = Path.of(entry.getName()).getFileName().toString();
                ManifestRow row = manifest.get(filename);
                if (row == null) {
                    failed.add(filename + " 未在 manifest 中描述");
                    continue;
                }
                try (InputStream in = zip.getInputStream(entry)) {
                    switch (row.type()) {
                        case "image" -> {
                            String fileId = storeGrid(in, filename, "image/*", row.csvId());
                            imageDocs.add(new PersonImage(null, row.csvId(), row.title(), fileId, row.description()));
                            images++;
                        }
                        case "video" -> {
                            String fileId = storeGrid(in, filename, "video/*", row.csvId());
                            videoDocs.add(new PersonVideo(null, row.csvId(), row.title(), fileId, null, row.description()));
                            videos++;
                        }
                        case "text" -> {
                            // 读取文本到字符串
                            String content = new String(in.readAllBytes(), StandardCharsets.UTF_8);
                            textDocs.add(new PersonText(null, row.csvId(), row.title(), content));
                            texts++;
                        }
                        default -> failed.add(filename + " 未识别的类型 " + row.type());
                    }
                } catch (Exception ex) {
                    log.error("处理文件 {} 失败", filename, ex);
                    failed.add(filename + " 错误: " + ex.getMessage());
                }
            }
            if (!imageDocs.isEmpty()) imageRepo.insert(imageDocs);
            if (!videoDocs.isEmpty()) videoRepo.insert(videoDocs);
            if (!textDocs.isEmpty())  textRepo.insert(textDocs);
        } finally {
            Files.deleteIfExists(tempZip);
        }
        return new BatchResult(images, videos, texts, manifestCount, failed);
    }

    private Map<String, ManifestRow> parseManifest(InputStream manifestStream) throws IOException, CsvValidationException {
        Map<String, ManifestRow> map = new HashMap<>();
        try (CSVReader reader = new CSVReader(new InputStreamReader(manifestStream, StandardCharsets.UTF_8))) {
            String[] header = reader.readNext(); // filename,csvId,type,title,description
            String[] row;
            while ((row = reader.readNext()) != null) {
                if (row.length < 3) continue; // 至少 filename csvId type
                String filename = row[0];
                Long csvId = Long.valueOf(row[1]);
                String type = row[2];
                String title = row.length > 3 ? row[3] : null;
                String desc = row.length > 4 ? row[4] : null;
                map.put(filename, new ManifestRow(filename, csvId, type, title, desc));
            }
        }
        return map;
    }

    /**
     * 将文件流写入 GridFS，并返回生成的 fileId。
     * 会尝试根据文件名自动推断 MIME 类型，保证客户端查看时更友好。
     */
    private String storeGrid(InputStream in, String filename, String fallbackContentType, Long csvId) throws IOException {
        String mime = URLConnection.guessContentTypeFromName(filename);
        if (mime == null) mime = fallbackContentType; // image/* 或 video/*

        Document meta = new Document("csvId", csvId)
                .append("filename", filename)
                .append("contentType", mime);

        return gridFsTemplate.store(in, filename, mime, meta).toHexString();
    }
} 